package org.odst.parser;

import java.io.IOException;
import java.io.StringReader;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.odftoolkit.odfdom.doc.table.OdfTableCell;
import org.odftoolkit.odfdom.doc.table.OdfTableRow;
import org.odftoolkit.odfdom.dom.element.table.TableTableCellElementBase;
import org.odst.exception.ParsePropertyException;
import org.odst.formula.Formula;
import org.odst.tag.Block;
import org.odst.tag.Tag;
import org.odst.tag.TagContext;
import org.odst.transformer.Configuration;
import org.odst.transformer.Row;
import org.odst.util.SheetHelper;
import org.odst.util.Util;
import org.xml.sax.SAXException;

/**
 * Class for parsing excel cell
 * 
 * @author Leonid Vysochyn
 * @author Cyril PODER
 */
public class CellParser {
	protected final Log log = LogFactory.getLog(getClass());

	private final Cell cell;

	private Configuration configuration;

	public CellParser(OdfTableCell hssfCell, Row row,
			Configuration configuration) {
		this.cell = new Cell(hssfCell, row);
		if (configuration != null) {
			this.configuration = configuration;
		} else {
			this.configuration = new Configuration();
		}
	}

	public CellParser(Cell cell) {
		this.cell = cell;
	}

	public Cell getCell() {
		return cell;
	}

	public Cell parseCell(Map<String, Object> beans) {
		if (cell.getOooCell() != null) {
			try {
				if (cell.getOooCell().getValueType() != null
						&& cell.getOooCell().getValueType().equals("string")) {
					cell.setOooCellValue(cell.getOooCell().getStringValue());
					parseCellValue(beans);
				}
			} catch (ParsePropertyException e) {
				log.error("Can't get value for property="
						+ cell.getCollectionProperty().getProperty(), e);
				throw new RuntimeException(e);
			}
			// updateMergedRegions();
		}
		return cell;
	}

	public Formula parseCellFormula() {
		if (cell.getOooCell() != null
				&& cell.getOooCell().getValueType() != null
				&& cell.getOooCell().getValueType().equals("string")) {
			cell.setOooCellValue(cell.getOooCell().getStringValue());
			if (cell.getOooCellValue().startsWith(
					configuration.getStartFormulaToken())
					&& cell.getOooCellValue().lastIndexOf(
							configuration.getEndFormulaToken()) > 0) {
				parseFormula();
			}
		}
		return cell.getFormula();
	}

	private void parseFormula() {
		// process formula cell
		if (cell.getExpressions().size() > 1) {
			String value = "";
			for (int i = 0; i < cell.getExpressions().size(); i++) {
				Expression expr = (Expression) cell
						.getExpressions().get(i);
				try {
					value += expr.evaluate();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			cell.setFormula(new Formula(value.substring(2, value
					.lastIndexOf(']'))));
			// setCellValue(cell, value);
			cell.getOooCell().setStringValue(value);
			cell.setOooCellValue(value);
		}
		int i = cell.getOooCellValue().lastIndexOf(
				configuration.getEndFormulaToken());
		String expr = cell.getOooCellValue().substring(2, i);
		cell.setFormula(new Formula(expr));
		cell.getFormula().setRowNum(cell.getOooCell().getRowIndex());
		cell.getFormula().setCellNum(cell.getOooCell().getColumnIndex());
		if (i + 1 < cell.getOooCellValue().length()) {
			String tail = cell.getOooCellValue().substring(i + 1);
			int j = tail.indexOf(configuration.getMetaInfoToken());
			if (j >= 0) {
				cell.setMetaInfo(tail.substring(j));
				if (j > 0) {
					cell.setLabel(tail.substring(0, j));
				}
				cell.setCollectionName(tail.substring(j + 2));
			} else {
				cell.setLabel(tail);
			}
		}
		cell.setStringCellValue(cell.getOooCellValue().substring(0, i + 1));
	}

	private void parseCellExpression(Map<String, Object> beans) {
		cell.setCollectionProperty(null);
		String curValue = cell.getOooCellValue();
		int depRowNum = 0;
		int j = curValue.lastIndexOf(configuration.getMetaInfoToken());
		if (j >= 0) {
			cell.setStringCellValue(cell.getOooCellValue().substring(0, j));
			cell.setMetaInfo(cell.getOooCellValue().substring(j + 2));
			String tail = curValue.substring(j + 2);
			// processing additional parameters
			// check if there is collection property name specified
			int k = tail.indexOf(":");
			if (k >= 0) {
				try {
					depRowNum = Integer.parseInt(tail.substring(k + 1));
				} catch (NumberFormatException e) {
					// ignore it if not an integer
				}
				cell.setCollectionName(tail.substring(0, k));
			} else {
				cell.setCollectionName(tail);
			}
			curValue = curValue.substring(0, j);
		} else {
			cell.setStringCellValue(cell.getOooCellValue());
		}

		try {
			while (curValue.length() > 0) {
				int i = curValue.indexOf(configuration
						.getStartExpressionToken());
				if (i >= 0) {
					int k = curValue.indexOf(configuration
							.getEndExpressionToken(), i + 2);
					if (k >= 0) {
						// new bean property found
						String expr = curValue.substring(i + 2, k);
						if (i > 0) {
							String before = curValue.substring(0, i);
							cell.getExpressions().add(
									new Expression(before, configuration));
						}
						Expression expression = new Expression(expr, beans,
								configuration);
						if (expression.getCollectionProperty() != null) {
							if (cell.getCollectionProperty() == null) {
								cell.setCollectionName(expression
										.getCollectionProperty()
										.getFullCollectionName());
								cell.setCollectionProperty(expression
										.getCollectionProperty());
								cell.setDependentRowNumber(depRowNum);
							} else {
								if (log.isInfoEnabled()) {
									log
											.info("Only the same collection property in a cell is allowed.");
								}
							}
						}
						cell.getExpressions().add(expression);
						curValue = curValue.substring(k + 1, curValue.length());
					} else {
						cell.getExpressions().add(
								new Expression(curValue, configuration));
						curValue = "";
					}
				} else {
					if (curValue.length() != cell.getOooCellValue().length()) {
						cell.getExpressions().add(
								new Expression(curValue, configuration));
					}
					curValue = "";
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			log.error("Can't parse expression", e);
		}
	}

	private void parseCellValue(Map<String, Object> beans)
			throws ParsePropertyException {
		if (cell.getOooCellValue() != null) {
			if (cell.getOooCellValue().startsWith(
					configuration.getStartFormulaToken())
					&& cell.getOooCellValue().lastIndexOf(
							configuration.getEndFormulaToken()) > 0) {
				parseCellExpression(beans);
				parseFormula();
			} else if (cell.getOooCellValue().startsWith(
					"<" + configuration.getTagPrefix())) {
				// String tagName = cell.getHssfCellValue().split("(?<=<" +
				// configuration.getTagPrefix() + ")\\w+", 2)[0];
				String tagName = getTagName(cell.getOooCellValue());
				if (tagName != null) {
					if (cell.getOooCellValue().endsWith("/>")) {
						Block tagBody = new Block(cell.getOooCell()
								.getRowIndex(), cell.getOooCell()
								.getColumnIndex(), cell.getOooCell()
								.getRowIndex(), cell.getOooCell()
								.getColumnIndex());
						parseTag(tagName, tagBody, beans, false);
					} else {
						OdfTableCell hssfCell = findMatchingPairInRow(tagName);
						if (hssfCell != null) {
							// closing tag is in the same row
							Block tagBody = new Block(cell.getOooCell()
									.getRowIndex(), cell.getOooCell()
									.getColumnIndex(), cell.getOooCell()
									.getRowIndex(), hssfCell.getColumnIndex());
							parseTag(tagName, tagBody, beans, true);
						} else {
							int hssfRow = findMatchingPair(tagName);
							if (hssfRow != -1) {
								// closing tag is in hssfRow
								Block tagBody = new Block(null, cell
										.getOooCell().getRowIndex(), hssfRow);
								parseTag(tagName, tagBody, beans, true);
							} else {
								log.error("Can't find matching tag pair for "
										+ cell.getOooCellValue() + " (Row : "
										+ cell.getOooCell().getRowIndex()
										+ ", Column : "
										+ cell.getOooCell().getColumnIndex()
										+ ")");
							}
						}
					}
				}
			} else {
				parseCellExpression(beans);
			}
		}
	}

	private OdfTableCell findMatchingPairInRow(String tagName) {
		int count = 0;
		TableTableCellElementBase currentCell = (TableTableCellElementBase) cell
				.getOooCell().getOdfElement().getNextSibling();
		while (currentCell != null) {
			OdfTableCell hssfCell = OdfTableCell.getInstance(currentCell);
			if (hssfCell != null/*
								 * &&
								 * hssfCell.getType().equals(CellContentType.TEXT
								 * )
								 */) {
				String cellValue = hssfCell.getDisplayText();
				if (cellValue.matches("<" + configuration.getTagPrefix()
						+ tagName + "\\b.*")) {
					count++;
				} else {
					if (cellValue.matches("</" + configuration.getTagPrefix()
							+ tagName + ">")) {
						if (count == 0) {
							return hssfCell;
						}
						count--;
					}
				}
			}
			currentCell = (TableTableCellElementBase) currentCell
					.getNextSibling();
		}
		return null;
	}

	private String getTagName(String xmlTag) {
		int i = configuration.getTagPrefix().length() + 1;
		int j = i;
		while (j < xmlTag.length()
				&& Character.isLetterOrDigit(xmlTag.charAt(j))) {
			j++;
		}
		if (j == xmlTag.length()) {
			log.warn("can't determine tag name");
			return null;
		}
		return xmlTag.substring(i, j);
	}

	private int findMatchingPair(String tagName) {
		int count = 0;
		int result = cell.getOooCell().getRowIndex() + 1;

		OdfTableRow currentRow = cell
				.getOooCell().getTableRow().getNextRow();
		TableTableCellElementBase currentCell = (TableTableCellElementBase) currentRow.getOdfElement()
				.getFirstChild();
		while(currentCell == null) {
			currentRow = SheetHelper.getNextRow(currentRow);
			currentCell = (TableTableCellElementBase) currentRow.getOdfElement().getFirstChild();
		}
		while (currentRow != null) {
			OdfTableCell hssfCell = OdfTableCell.getInstance(currentCell);
			String cellValue = hssfCell.getStringValue();
			if (cellValue.matches("<" + configuration.getTagPrefix() + tagName
					+ "\\b.*")) {
				count++;
			} else {
				if (cellValue.matches("</" + configuration.getTagPrefix()
						+ tagName + ">")) {
					if (count == 0) {
						return result;
					}
					count--;
				}
			}
			currentCell = (TableTableCellElementBase) currentCell
					.getNextSibling();
			if (currentCell == null) {
				currentRow = SheetHelper.getNextRow(currentRow);
				if (currentRow != null)
					currentCell = (TableTableCellElementBase) currentRow.getOdfElement().getFirstChild();
				result++;
			}
		}
		return -1;
	}

	private void parseTag(String tagName, Block tagBody,
			Map<String, Object> beans, boolean appendCloseTag) {

		String xml = null;

		try {
			if (appendCloseTag) {
				xml = configuration.getJXLSRoot() + cell.getOooCellValue()
						+ "</" + configuration.getTagPrefix() + tagName + ">"
						+ configuration.getJXLSRootEnd();
			} else {
				xml = configuration.getJXLSRoot() + cell.getOooCellValue()
						+ configuration.getJXLSRootEnd();
			}
			if (configuration.getEncodeXMLAttributes()) {
				xml = Util.escapeAttributes(xml);
			}
			Tag tag = (Tag) configuration.getDigester().parse(
					new StringReader(xml));
			if (tag == null) {
				throw new RuntimeException("Invalid tag: " + tagName);
			}
			cell.setTag(tag);
			TagContext tagContext = new TagContext(cell.getRow().getSheet(),
					tagBody, beans);
			tag.init(tagContext);
		} catch (IOException e) {
			log.warn("Can't parse cell tag " + cell.getOooCellValue()
					+ ": fullXML: " + xml, e);
			throw new RuntimeException("Can't parse cell tag "
					+ cell.getOooCellValue() + ": fullXML: " + xml, e);
		} catch (SAXException e) {
			log.warn("Can't parse cell tag " + cell.getOooCellValue()
					+ ": fullXML: " + xml, e);
			throw new RuntimeException("Can't parse cell tag "
					+ cell.getOooCellValue() + ": fullXML: " + xml, e);
		}
	}

	// private void updateMergedRegions() {
	// XCellAddressable cellAddressable =
	// (XCellAddressable)UnoRuntime.queryInterface(XCellAddressable.class,
	// cell.getHssfCell());
	// cell.setMergedRegion(Util.getMergedRegion(
	// cell.getRow().getSheet().getHssfSheet(),
	// cellAddressable.getCellAddress().Row,
	// cellAddressable.getCellAddress().Column ));
	// }
}
